/*
 * Copyright (C) 2010-2011 Comcast Cable.  All Rights Reserved.
 */
package com.hercules.android.receiver.painting.resources;

import android.graphics.Rect;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnErrorListener;
import android.media.MediaPlayer.OnPreparedListener;
import android.net.Uri;
import android.widget.MediaController;
import android.widget.VideoView;

import com.hercules.android.receiver.LogUtil;
import com.hercules.android.receiver.model.commands.XRECallCommand;
import com.hercules.android.receiver.model.commands.XRECommand;
import com.hercules.android.receiver.model.commands.XRESetCommand;
import com.hercules.android.receiver.model.objects.XREVideo;
import com.hercules.android.receiver.painting.PaintersUtil;
import com.hercules.android.receiver.painting.XREObjectPainterException;
import com.hercules.android.receiver.painting.views.XRELayoutParams;

/**
 * Painter for {@link XREVideo} resource. First it will setup video holder, {@link VideoView}, and then it will use it
 * depending on commands.
 * <p>
 * AndroidAssembly1 this class was added in Android Assembly 1
 * <p>
 * <b>Thread safety:</b> Not thread safe, because base class is not thread safe
 *
 * @author 7realm
 * @version 1.0
 */
public class XREVideoPainter extends BaseXREResourcePainter implements OnErrorListener, OnPreparedListener {
    /**
     * Represents the {@link VideoView} that will be used to display video.
     * <p>
     * It is created in init() method, modified in handleCommand() method and released in finish() method.
     * <p>
     * AndroidAssembly3 this field was added in Android Assembly 3.
     */
    private VideoView videoHolder;

    /**
     * Represents the measured video width.
     * <p>
     * It is initialized in onPrepare() method and used in updateLayout().
     * <p>
     * AndroidAssembly3 this field was added in Android Assembly 3.
     */
    private int videoWidth;

    /**
     * Represents the measured video height.
     * <p>
     * It is initialized in onPrepare() method and used in updateLayout().
     * <p>
     * AndroidAssembly3 this field was added in Android Assembly 3.
     */
    private int videoHeight;

    /**
     * Constructor for class {@link XREVideoPainter}.
     *
     * @param xreResource the {@link XREVideo}
     */
    public XREVideoPainter(XREVideo xreResource) {
        super(xreResource);
    }

    /**
     * Performs initialize actions.
     *
     * @throws IllegalStateException if required fields are not set
     * @throws XREObjectPainterException if video content options are not correct
     */
    @Override
    public void init() throws XREObjectPainterException {
        final String methodName = "init";
        LogUtil.enter(getLogger(), this, methodName);

        try {
            PaintersUtil.checkState(getReceiver(), "receiver");
            PaintersUtil.checkState(getXREView(), "xreView");
            PaintersUtil.checkState(getAndroidView(), "androidView");

            final XREVideo video = getXREVideo();

            // AndroidAssembly3 video URL is actually stored in content options
            if (video.getContentOptions() == null || video.getContentOptions().isEmpty()) {
                throw new XREObjectPainterException("Video content options are empty. Raw url must be present.");
            }
            final String url = video.getContentOptions().get(0).get("rawURL");
            if (url == null) {
                throw new XREObjectPainterException(
                    "Video content options are not correct. Missing required item 'rawURL'.");
            }

            // add view to parent
            getAndroidView().runOnUI(new Runnable() {
                public void run() {
                    synchronized (XREVideoPainter.this) {
                        // setup the video view
                        videoHolder = new VideoView(getAndroidView().getContext());
                    }

                    videoHolder.setMediaController(new MediaController(getAndroidView().getContext()));
                    videoHolder.setVideoURI(Uri.parse(url));

                    // AndroidAssembly3 added listeners and layout params
                    // listeners for errors and video preparation
                    videoHolder.setOnErrorListener(XREVideoPainter.this);
                    videoHolder.setOnPreparedListener(XREVideoPainter.this);

                    // set temporary layout, until video size is not known
                    videoHolder.setLayoutParams(new XRELayoutParams(getXREView()));

                    // add to XREView
                    getAndroidView().addView(videoHolder);

                    // if autoplay is on, play the audio
                    if (video.isAutoPlay()) {
                        videoHolder.start();
                    }

                }
            });
        } catch (IllegalStateException e) {
            throw LogUtil.error(getLogger(), this, methodName, e);
        } catch (XREObjectPainterException e) {
            throw LogUtil.error(getLogger(), this, methodName, e);
        }

        LogUtil.exit(getLogger(), this, methodName);
    }

    /**
     * Performs clean up.
     *
     * @throws IllegalStateException if required fields are not set
     */
    @Override
    public void finish() {
        String methodName = "finish";
        LogUtil.enter(getLogger(), this, methodName);

        try {
            PaintersUtil.checkState(getReceiver(), "receiver");
            synchronized (this) {
                PaintersUtil.checkState(videoHolder, "videoHolder");
            }

            // stop the video
            videoHolder.stopPlayback();
        } catch (IllegalStateException e) {
            throw LogUtil.error(getLogger(), this, methodName, e);
        }

        LogUtil.exit(getLogger(), this, methodName);
    }

    /**
     * Handle the {@link XRECommand}.
     *
     * @param command the incoming command
     * @throws IllegalArgumentException if command is null
     * @throws IllegalStateException if receiver, xreView or attachedResource is null, or if the attached resource
     * cannot be converted to the right type.
     */
    @Override
    public void handleCommand(XRECommand command) {
        String methodName = "finish";
        LogUtil.enter(getLogger(), this, methodName);

        try {
            PaintersUtil.checkNull(command, "command");
            PaintersUtil.checkState(getReceiver(), "receiver");
            PaintersUtil.checkState(getXREView(), "xreView");

            synchronized (this) {
                // if the command is play, then play it; if the command is stop, then stop it, ignore all other commands
                if (videoHolder != null && command instanceof XRECallCommand) {
                    XRECallCommand callCommand = (XRECallCommand) command;
                    if ("play".equals(callCommand.getMethod())) {
                        videoHolder.start();
                    } else if ("stop".equals(callCommand.getMethod())) {
                        videoHolder.stopPlayback();
                    }
                } else if (command instanceof XRESetCommand) {
                    // update layout if there is set command
                    updateLayout();
                }
            }
        } catch (IllegalArgumentException e) {
            throw LogUtil.error(getLogger(), this, methodName, e);
        } catch (IllegalStateException e) {
            throw LogUtil.error(getLogger(), this, methodName, e);
        }

        LogUtil.exit(getLogger(), this, methodName);
    }

    /**
     * Error listener for {@link VideoView}.
     *
     * @param mp the current {@link MediaPlayer} that raised error
     * @param what the error code
     * @param extra the error extra code
     * @return always true
     */
    @SuppressWarnings("boxing")
    public boolean onError(MediaPlayer mp, int what, int extra) {
        LogUtil.warn(getLogger(), this, "onError", "Video related error occurred. Code {0} extra {1}. Url {2}.", what,
            extra, getXREVideo().getUrl());
        return true;
    }

    /**
     * This method will be called when video will be prepared. We will use it get calculated video size.
     *
     * @param mediaplayer the current {@link MediaPlayer}
     */
    public void onPrepared(MediaPlayer mediaplayer) {
        videoWidth = mediaplayer.getVideoWidth();
        videoHeight = mediaplayer.getVideoHeight();

        synchronized (this) {
            // recalculate layout
            updateLayout();
        }
    }

    /**
     * Gets {@link XREVideo}.
     *
     * @return the {@link XREVideo}
     */
    private XREVideo getXREVideo() {
        return (XREVideo) getXREResource();
    }

    /**
     * Updates layout of the {@link XREVideo} resource. Applies stretch and align to video.
     */
    private void updateLayout() {
        if (videoHolder != null && videoHeight != 0 && videoWidth != 0) {
            Rect rect = PaintersUtil.calculateStretchAndAling(getXREView(), videoWidth, videoHeight);

            // update layout
            videoHolder.setLayoutParams(new XRELayoutParams(rect.left, rect.top, rect.width(), rect.height()));
        }
    }
}
